package com.wiscamp.ninechapters.problems.infrastructure.repositories;

import com.wiscamp.ninechapters.problems.domain.models.sets.ProblemSet;
import com.wiscamp.ninechapters.problems.domain.models.sets.SetProblem;
import com.wiscamp.ninechapters.problems.domain.repositories.ProblemSetRepository;
import com.wiscamp.ninechapters.problems.infrastructure.converters.ProblemSetConverter;
import com.wiscamp.ninechapters.problems.infrastructure.mappers.sets.ProblemSetMapper;
import com.wiscamp.ninechapters.problems.infrastructure.mappers.sets.ProblemSetViewMapper;
import com.wiscamp.ninechapters.problems.infrastructure.mappers.sets.SetProblemMapper;
import com.wiscamp.ninechapters.problems.infrastructure.po.problems.ProblemPO;
import com.wiscamp.ninechapters.problems.infrastructure.po.sets.ProblemSetPO;
import com.wiscamp.ninechapters.problems.infrastructure.po.sets.ProblemSetView;
import com.wiscamp.ninechapters.problems.infrastructure.po.sets.RegroupRulePO;
import com.wiscamp.ninechapters.problems.infrastructure.po.sets.SetProblemPO;
import cube.ddd.aspect.HandleDataException;
import cube.ddd.common.query.PagedObjects;
import cube.ddd.common.query.PagedQuery;
import cube.ddd.domain.BaseRepository;
import cube.ddd.utils.PersistenceUtils;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.springframework.stereotype.Repository;

import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Objects;
import java.util.stream.Collectors;

@Repository
public class ProblemSetRepositoryImpl extends BaseRepository implements ProblemSetRepository {
    private final ProblemSetMapper problemSetMapper;

    private final ProblemSetViewMapper problemSetViewMapper;

    private final SetProblemMapper setProblemMapper;

    public ProblemSetRepositoryImpl(ProblemSetMapper problemSetMapper, ProblemSetViewMapper problemSetViewMapper, SetProblemMapper setProblemMapper) {
        this.problemSetMapper = problemSetMapper;
        this.problemSetViewMapper = problemSetViewMapper;
        this.setProblemMapper = setProblemMapper;
    }

    // region problem set
    private ProblemSetPO getProblemSetPO(@Min(0) long setId) {
        return PersistenceUtils.findById(setId, problemSetMapper::findById);
    }

    private void loadDetails(ProblemSet set) {
        if (Objects.nonNull(set)) {
            var setProblems = getSetProblemsBySet(set.getSetId());
            set.setSetProblems(setProblems);
        }
    }

    /**
     * Gets a problem set
     *
     * @param setId
     * @return
     */
    @HandleDataException
    public ProblemSet getProblemSet(@Min(0) long setId) {
        var set = PersistenceUtils.findEntityById(setId, problemSetViewMapper::findById,
                ProblemSetConverter.Instance::toProblemSetByView);
        loadDetails(set);
        return set;
    }

    /**
     * Gets a problem set
     *
     * @param setCode
     * @param organizationId
     * @return
     */
    @HandleDataException
    public ProblemSet getProblemSetByCode(@NotBlank String setCode, @Min(0) long organizationId) {
        var set = PersistenceUtils.getEntityByTextAndId(setCode, organizationId, problemSetViewMapper::findBySetCode,
                ProblemSetConverter.Instance::toProblemSetByView);
        loadDetails(set);
        return set;
    }

    /**
     * Gets problem sets
     *
     * @param setName
     * @param organizationId
     * @return
     */
    @HandleDataException
    public Collection<ProblemSet> getProblemSetsByName(@NotBlank String setName, @Min(0) long organizationId) {
        var sets = PersistenceUtils.getEntitiesByTextAndId(setName, organizationId, problemSetViewMapper::findListBySetName,
                ProblemSetConverter.Instance::toProblemSetByView);
        for (var set : sets) {
            loadDetails(set);
        }
        return sets;
    }

    /**
     * Gets problem sets by organization
     *
     * @param organizationId
     * @return
     */
    @HandleDataException
    public Collection<ProblemSet> getProblemSetsByOrganization(@Min(0) long organizationId) {
        var sets = PersistenceUtils.getEntitiesById(organizationId, problemSetViewMapper::findListByOrganization,
                ProblemSetConverter.Instance::toProblemSetByView);
        for (var set : sets) {
            loadDetails(set);
        }
        return sets;
    }

    /**
     * Gets problem sets by a regroup rule
     *
     * @param ruleId
     * @return
     */

    @HandleDataException
    public Collection<ProblemSet> getProblemSetsByRule(@Min(0) long ruleId) {
        var sets = PersistenceUtils.getEntitiesById(ruleId, problemSetViewMapper::findListByRule,
                ProblemSetConverter.Instance::toProblemSetByView);
        for (var set : sets) {
            loadDetails(set);
        }
        return sets;
    }

    /**
     * Gets problem sets by pager
     *
     * @param query
     * @return
     */
    @HandleDataException
    public PagedObjects<ProblemSet> getProblemSetsByQuery(@NotNull PagedQuery query) {
        PagedObjects<ProblemSetView> pagedObjects = PersistenceUtils.getPersistenceObjectsByQuery(query, problemSetViewMapper::findAll);
        return PersistenceUtils.convertPagedObjects(pagedObjects, ProblemSetConverter.Instance::toProblemSetByView);
    }

    /**
     * Gets problem sets created by a user
     * 根据创建人和时间区间获取习题集编号列表
     *
     * @param creatorId
     * @param startDate
     * @param endDate
     * @return
     */
    @HandleDataException
    public Collection<Long> getProblemSetIdsByCreatorAndDate(@Min(0) long creatorId, LocalDateTime startDate, LocalDateTime endDate) {
        var sets = problemSetViewMapper.findListByCreatorAndDate(creatorId, startDate, endDate);
        return sets.stream().map(ProblemSetView::getSetId).collect(Collectors.toList());
    }

    @HandleDataException
    public Collection<Long> getProblemIdsBySet(@Min(0) long setId) {
        return setProblemMapper.findProblemIdsBySet(setId);
    }

    /**
     * Gets id collection of items by problem sets
     *
     * @param setIds
     * @return
     */
    @HandleDataException
    public Collection<Long> getProblemIdsBySets(@NotEmpty Collection<Long> setIds) {
        return setProblemMapper.findProblemIdsBySetIds(setIds);
    }

    /**
     * Creates a problem set
     *
     * @param set
     * @return
     */
    @HandleDataException
    public ProblemSet saveProblemSet(@NotNull ProblemSet set) {
        var newId = set.getSetId();
        if (set.getSetId() <= 0) {
            var newSet = PersistenceUtils.createEntity(set, problemSetMapper::saveAndFlush,
                    ProblemSetConverter.Instance::toProblemSetPO, ProblemSetConverter.Instance::toProblemSet);
            newId = newSet.getSetId();

            for (var setProblem : set.getSetProblems()) {
                setProblem.setSetId(newId);
                saveSetProblem(setProblem);
            }

        } else {
            var existedPO = getProblemSetPO(set.getSetId());
            if (existedPO == null) existedPO = new ProblemSetPO();
            PersistenceUtils.updateEntity(set, existedPO, problemSetMapper::saveAndFlush,
                    ProblemSetConverter.Instance::toProblemSetPO, ProblemSetConverter.Instance::toProblemSet);

            for (var setProblem : set.getSetProblems()) {
                setProblem.setSetId(newId);
                saveSetProblem(setProblem);
            }
        }

        return getProblemSet(newId);
    }

    /**
     * Gets score of a correct item
     *
     * @param problem
     * @param rule
     * @return
     */
    private double getProblemScoreByDifficulty(@NotNull ProblemPO problem, @NotNull RegroupRulePO rule) {
        double score;
        if (rule != null && rule.getTotalScore() > 0) {
            score = rule.getTotalScore() / rule.getProblemQuantity();
        } else {
            // 根据难度值，每20分一个级别，题目分数从1递增
            score = problem.getDifficulty() / 20;
        }
        if (score <= 0)
            score = 1;
        return score;
    }

    /**
     * Changes name of a problem set
     *
     * @param setId
     * @param setName
     * @return
     */
    @HandleDataException
    public boolean changeProblemSetName(@Min(0) long setId, @NotBlank String setName, @Min(0) long operatorId) {
        problemSetMapper.updateSetName(setName, setId);
        return true;
    }

    // endregion

    // region set problem

    public Collection<SetProblem> getSetProblemsBySet(@Min(0) long setId) {
        return PersistenceUtils.getEntitiesById(setId, setProblemMapper::findListBySet,
                ProblemSetConverter.Instance::toSetProblem);
    }

    /**
     * Gets id collection of items in history problem sets daily challenge
     *
     * @return
     */
    public Collection<Long> getProblemIdsByChallengeHistory() {
        return problemSetViewMapper.findProblemIdsByChallengeHistory();
    }

    /**
     * Removes item from problem set
     *
     * @param setId
     * @param problemId
     * @return
     */

    @HandleDataException
    public boolean removeSetProblem(@Min(0) long setId, @Min(0) long problemId) {
        setProblemMapper.removeSetProblem(setId, problemId);
        return true;
    }


    /**
     * Gets maximum item index of a problem set
     *
     * @param setId
     * @return
     */
    @HandleDataException
    public int getMaxProblemIndex(@Min(0) long setId) {
        try {
            return setProblemMapper.findMaxProblemIndex(setId);
        } catch (Exception ex) {
            // 此问题在创建新习题集的第一题时常见，所以异常无需处理
            ex.printStackTrace();
        }
        return 0;
    }
    // endregion

    private SetProblemPO getSetProblemPO(long id) {
        return PersistenceUtils.findById(id, setProblemMapper::findById);
    }

    public SetProblem getSetProblem(long id) {
        return PersistenceUtils.findEntityById(id, setProblemMapper::findById, ProblemSetConverter.Instance::toSetProblem);
    }

    @HandleDataException
    public SetProblem saveSetProblem(@NotNull SetProblem setProblem) {
        var newId = setProblem.getId();
        if (setProblem.getId() <= 0) {
            var newSetProblem = PersistenceUtils.createEntity(setProblem, setProblemMapper::saveAndFlush,
                    ProblemSetConverter.Instance::toSetProblemPO, ProblemSetConverter.Instance::toSetProblem);
            newId = newSetProblem.getId();
        } else {
            var existedPO = getSetProblemPO(setProblem.getId());
            if (existedPO == null) existedPO = new SetProblemPO();
            PersistenceUtils.updateEntity(setProblem, existedPO, setProblemMapper::saveAndFlush,
                    ProblemSetConverter.Instance::toSetProblemPO, ProblemSetConverter.Instance::toSetProblem);
        }

        return getSetProblem(newId);
    }

}
